from itertools import combinations
import matplotlib.pyplot as plt
import networkx as nx

from data_init import initPoint, initEdge
import itertools


def combine(temp_list, n):
    """根据n获得列表中的所有可能组合（n个元素为一组）"""
    temp_list2 = []
    for c in combinations(temp_list, n):
        temp_list2.append(c)
    return temp_list2


def veryOneShortestTime(librarySelect: tuple, points: list, G: nx.Graph) -> map:
    """计算每个点到每个仓的最短距离"""
    allMinPathMap = {}  # 所有点的到仓的最短路径
    for point in points:
        pointMinPathMap = {}  # 一个点到所有仓的最短路径
        for library in librarySelect:
            minWPathV = round(
                nx.dijkstra_path_length(G, source=point.id, target=library.id), 2
            )
            pointMinPathMap[library.id] = minWPathV  # 注入点到仓的最短路径
            # print(f"{point.id}-{library.id}的最短路径:{minWPathV}")
        allMinPathMap[point.id] = pointMinPathMap  # 注入点到所有仓的路径
    return allMinPathMap


def pointBind(minPathMap: map, points: list, librarys: list):
    """点与仓绑定"""
    pointMap = {item.id: item for item in points}
    libraryMap = {item.id: item for item in librarys}

    for point in points:  # 遍历点
        pointMinPathMap = minPathMap[point.id]  # 获取点到所有仓的最短路径
        min_key = min(pointMinPathMap.items(), key=lambda x: x[1])[0]
        # print(f"点{point.id}最近的仓是{min_key},路径：{pointMinPathMap}")
        library = libraryMap[min_key]
        library.points.append(point)  # 先绑定
        library.residue -= point.request  # 扣减剩余量
        # yTotal = sum(list(map(lambda x: x.request, library.points)))  # 需求总量

        if library.residue < 0:  # 仓剩余量小于0，表示需要动态调整
            # print(f"点{point.id}-仓{min_key},需求量：{point.request},需求总量:{yTotal}")
            exceed = 0 - library.residue  # 计算超出量
            exceedIds = [
                point.id for point in library.points if point.request >= exceed
            ]  # 已经绑定的点中需求量大于超出量的点的id
            print(
                f">>>需要动态调整,点{point.id}，计划绑定仓{library.id},绑定后超出量:{exceed},绑定后仓剩余:{library.residue},仓已绑定点中不小于超出量的id:{exceedIds}"
            )
            filtered_map = {
                key: value for key, value in minPathMap.items() if key in exceedIds
            }  # 根据exceedIds获取到每个点到每个仓的最短路径map filtered_map
            otherLib = [lib for lib in librarys if lib.id != library.id]  # 除当前仓之外的其他仓
            minVPointMap = {}  # 最小值路径对应的点和仓
            for pId, pathMap in filtered_map.items():
                # pointMap[pId]
                libIds = [
                    lib.id for lib in otherLib if pointMap[pId].request <= lib.residue
                ]  # 剩余容量不小于点需求量的仓id

                if len(libIds) == 0:  # 若没有仓满足容量条件，直接跳过
                    combine
                # 根据仓的id过滤路径
                filter_pathMap = {k: v for k, v in pathMap.items() if k in libIds}
                min_map = min(filter_pathMap.items(), key=lambda x: x[1])
                minVPointMap[min_map[1]] = {"pointId": pId, "libraryId": min_map[0]}
            # 若 minVPointMap 是空的，则表示没有可以调配的点和仓,表示动态调整失败，方案不同
            if len(minVPointMap) == 0:
                raise Exception(f"此方案不通,在点仓绑定关系如下时：{librarys},点{point}无法再绑定")
            # 选取最小值
            pointLibMap = min(minVPointMap.items(), key=lambda x: x[0])[1]
            # 先从当前library中剔除
            for point in library.points:
                if point.id == pointLibMap["pointId"]:
                    library.points.remove(point)
                    library.residue += point.request
                    # 再绑定到目标library
                    libraryMap[pointLibMap["libraryId"]].points.append(point)
                    libraryMap[pointLibMap["libraryId"]].residue -= point.request
                    print(
                        f">>>动态调整方案：点{point.id}，目标仓{pointLibMap['libraryId']},绑定后仓剩余:{libraryMap[pointLibMap['libraryId']].residue},点到各仓距离：{pointMinPathMap}"
                    )


def allCost(librarys: list, points: list, minPathMap: map, path_min_cost: float) -> map:
    FCost = 0  # 仓建设成本
    ACost = 0  # 仓经营成本
    PCost = 0  # 点运输惩罚成本
    c1 = 0.00025  # 等待惩罚 w/min
    c2 = 0.000333  # 迟到惩罚 w/min
    for library in librarys:
        FCost += library.construct_cost
        ACost += library.operat_cost

        # for point in library.points:
        #     t = minPathMap[point.id][library.id]
        #     if t < point.response_time_start:
        #         PCost += (point.response_time_start - t) * c1
        #     elif t > point.response_time_end:
        #         PCost += (t - point.response_time_end) * c2
    AllCost = FCost + ACost + PCost + path_min_cost
    return {"FCost(仓建设成本)": FCost, "ACost(仓经营成本)": ACost,
            "MPathCost(最短路径总运输费用)": path_min_cost,
            "AllCost(总成本)": AllCost}


def show(title: str, G: nx.Graph, library: list):
    print(f">>>连通图参数>>>title:{title},library:{library}")
    # 显示图形
    pos = nx.spring_layout(G)
    # pos = nx.shell_layout(G)

    plt.figure(num=title, figsize=(10, 8))
    nx.draw_networkx(G, pos, node_size=100, font_size=7)
    # nx.draw_networkx(G, pos, with_labels=True, node_color='white', edge_color='red', node_size=400, alpha=0.5)

    labels = nx.get_edge_attributes(G, 'weight')
    nx.draw_networkx_edge_labels(G, pos, edge_labels=labels, font_size=7)
    nx.draw_networkx_nodes(
        G, pos, nodelist=library, node_color="r", node_size=100
    )
    plt.show()


def allLengthPath(start, pathList: list, edgeMap: map) -> float:
    all_len = 0.0
    for i in range(0, len(pathList) - 1):
        a = pathList[i]
        b = pathList[i + 1]
        edge = edgeMap.get(str(a) + str(b)) if str(a) + str(b) in edgeMap != None else edgeMap.get(str(b) + str(a))
        if edge == None:
            return None
        all_len += edge['length']
    return all_len


def calculatePath(library: int, points: list, pointMap: map, edgeMap: map, G: nx.Graph) -> float:
    """计算仓遍历整个路径的结果"""
    unit_transport_cost = 5  # 单位 元/km
    c1 = 150  # 等待成本 单位元/min
    c2 = 200  # 超时成本 单位元/min

    time = 0  # 所用时间 min
    cost = 0  # 总费用  元

    # 仓 放到第一个节点
    points.insert(0, library)

    # 按顺序 遍历每个点
    for i in range(0, len(points) - 1):
        a = points[i]
        b = points[(i + 1) % len(points)]
        fd_time = round(
            nx.dijkstra_path_length(G, source=a, target=b), 2
        )
        if fd_time == None:
            return None

        # 获取路径信息
        path: list = nx.shortest_path(G, a, b)
        if path == None:
            return None

        # 根据路径计算长度
        path_len = allLengthPath(a, path, edgeMap)
        if path_len == None:
            return None

        cost += round(path_len * unit_transport_cost, 2)  # 运输费用累加
        time += fd_time  # 时间累加

        if (i + 1) % len(points) == 0:
            continue
        # 获取目的点信息
        tar_point = pointMap[b]

        # 时间惩罚
        if time < tar_point.response_time_start:
            cost += round((tar_point.response_time_start - time) * c1, 2)
        if time > tar_point.response_time_end:
            cost += round((time - tar_point.response_time_end) * c2, 2)

        time += tar_point.service_time  # 增加服务时间

    return cost


def optimalPath(libray: int, points: list, G: nx.Graph):
    """最优路径"""
    pointMap = {item.id: item for item in initPoint()}
    edgeMap = {str(e['startP']) + str(e['endP']): e for e in initEdge()}

    # 全排列
    permutations_lst = list(itertools.permutations(points))

    print(f"  >>> 全排列数量:{len(permutations_lst)}    {libray}->{points}")
    # 计算每一种方式的结果
    minResult = None
    minPath = None
    for e in permutations_lst:
        result = calculatePath(libray, list(e), pointMap, edgeMap, G)
        if result == None:
            continue
        if minPath == None:
            minResult = result
            minPath = e
        elif minResult < result:
            minPath = e
            minResult = result

    # minResult 之前是以元为单位, 还未万元返回
    return (minResult / 10000, e)
